/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package controller;

import exception.PersonnePresenteDansAscenseurException;
import exception.VitesseTropEleveException;
import java.util.*;
import model.AppelAscenseur;
import model.Ascenseur;
import model.Etage;
import model.Trajet;
import util.Observeur;

/**
 *
 * @author benoit
 */
public class AscenseurControleur {
    private Map<Integer,Ascenseur> ascenseurs;
    private EtageControleur etageControleur;
    private DateControleur dateControleur;

    public AscenseurControleur(Map<Integer,Ascenseur> ascenseurs,EtageControleur etageControleur, DateControleur dateControleur) {
        this.ascenseurs = ascenseurs;
        this.etageControleur = etageControleur;
        this.dateControleur = dateControleur;
    }
    
    void ajouterAppelAscenseur(int numero, AppelAscenseur a) {
        Ascenseur as = ascenseurs.get(numero);
        as.getTrajetsEnAttente().add(new Trajet(a, numero, as.getEtageCourant()));
    }
    
    public void majPositionAscenseurs() throws VitesseTropEleveException, PersonnePresenteDansAscenseurException {
        Trajet tr=null;
        Etage prochaineEtageArrive=null,prochaineEtageDepart=null,prochaineEtage=null;
        List<Trajet> trajetsATraiter = null;
        List<Trajet> trajetsSelectionnes = new ArrayList<Trajet>() ;
        int delaiPourArret=0;
        
        for(Ascenseur as : ascenseurs.values()) {
            // TODO : 1/1000 de tomber en panne
            if(as.getStatut() == Ascenseur.ST_EN_COURS_DE_TRAJET) {
                // TODO : Vérifier que le nombre de personne n'excède pas 15 
                /* Recherche des trajets en attente qui peuvente être traiter */
                tr = as.getTrajetsEnCours().get(0);
                if(!as.getTrajetsEnAttente().isEmpty()) {
                    for(Trajet t: as.getTrajetsEnAttente()) {
                        /* Il faut que le trajet soit dans le même sens et que la vitesse actuel de l'ascenseur permette de s'arrêter */
                        if(t.getSensTrajet() == tr.getSensTrajet()) {
                            /* Il faut que le trajet soit sur le chemin de l'ascenseur */
                            if(t.getSensTrajet() == Trajet.MONTE) {
                                if(t.getEtageDepart().getNumero() >= as.getEtageCourant().getNumero()) {
                                    if(Math.abs(t.getEtageDepart().getNumero() - as.getEtageCourant().getNumero()) >= as.getNbrEtagePourArretLePlusProche()) {
                                        trajetsSelectionnes.add(t);
                                        as.getTrajetsEnCours().add(t);
                                    }
                                }
                            } else {
                                if(t.getEtageDepart().getNumero() <= as.getEtageCourant().getNumero()) {
                                    if(Math.abs(t.getEtageDepart().getNumero() - as.getEtageCourant().getNumero()) >= as.getNbrEtagePourArretLePlusProche()) {
                                        trajetsSelectionnes.add(t);
                                        as.getTrajetsEnCours().add(t);
                                    }
                                }
                            }
                        }
                    }
                    
                    /* Retirer les trajets sélectionné des trajets en attente 
                     * On doit procéder comme ça pour éviter les pb d'accès
                     * concurrentiels */
                    for(Trajet t:trajetsSelectionnes) {
                        as.getTrajetsEnAttente().remove(t);
                    }
                }
                
                /* Recherche du prochaine arret à déservir */
                prochaineEtageArrive = getProchaineEtageArrive(as.getNumero());
                prochaineEtageDepart = getProchaineEtageDepart(as.getNumero());
                if(prochaineEtageDepart == null) {
                    prochaineEtage = prochaineEtageArrive;
                } else if(prochaineEtageDepart.equals(prochaineEtageArrive)) {
                    prochaineEtage = prochaineEtageDepart; // Peu importe
                } else if(tr.getSensTrajet() == Trajet.MONTE) {
                    if(prochaineEtageDepart.getNumero() < prochaineEtageArrive.getNumero()) {
                        prochaineEtage = prochaineEtageDepart;
                    } else {
                        prochaineEtage = prochaineEtageArrive;
                    }
                } else {
                    if(prochaineEtageDepart.getNumero() > prochaineEtageArrive.getNumero()) {
                        prochaineEtage = prochaineEtageDepart;
                    } else {
                        prochaineEtage = prochaineEtageArrive;
                    }
                }
                
                /* Si on vient d'arriver à l'étage de départ ou d'arriver */
                if(!as.isSurLeDepart() && as.getEtageCourant().equals(prochaineEtage)) {
                    // TODO : Gérer 15 personnes par ascenseur
                    as.setPorteOuverte(true);
                    as.setDelaiAvantProchaineEtage(5);
                    as.setVitesse(Ascenseur.VITESSE_0);
                    as.setDirectionActuel(Ascenseur.IMMOBILE);
                    as.setSurLeDepart(true);
                    
                    /* Personnes à déposer */
                    trajetsATraiter = getTrajetEtageArriveEgale(prochaineEtage, as.getNumero());
                    for(Trajet t : trajetsATraiter) {
                        as.decrementerNbPersonne(t.getNbrPersonne());
                        as.getTrajetsEnCours().remove(t);
                        t.setHeureArrivee(new model.Date(dateControleur.getHeure(), dateControleur.getMinute(), dateControleur.getSeconde()));
                        as.getTrajets().add(t);
                    }
                    
                    /* Personnes à chercher */
                    trajetsATraiter = getTrajetEtageDepartEgale(prochaineEtage, as.getNumero());
                    for(Trajet t : trajetsATraiter) {
                        as.incrementerNbPersonne(t.getNbrPersonne());
                        t.setHeureArriveEtageDepart(new model.Date(dateControleur.getHeure(), dateControleur.getMinute(), dateControleur.getSeconde()));
                    }
                    
                    /* Si il n'y a plus de travail on met l'ascenseur en arrivé */
                    if(as.getTrajetsEnCours().isEmpty()) {
                        as.setDelaiAvantProchaineEtage(5);
                        as.setStatut(Ascenseur.ST_ARRIVE);
                    }
                /* C'est qu'on est en train de traiter un ou des appels */
                } else if(as.isPorteOuverte() && as.isSurLeDepart()) {
                    accelerer(as.getNumero());
                    if(as.getDelaiAvantProchaineEtage() == 0) {
                        as.setPorteOuverte(false);
                        as.setVitesse(Ascenseur.VITESSE_1);
                        as.setSurLeDepart(true);
                        majMouvement(as.getNumero());
                    }
                } else {
                    if(Math.abs(prochaineEtage.getNumero() - as.getEtageCourant().getNumero()) > 2) {
                        accelerer(as.getNumero());
                    } else {
                        ralentir(as.getNumero());
                    }
                    
                    if(as.getDelaiAvantProchaineEtage() == 0) {
                        if(as.getDirectionActuel() == Ascenseur.MONTEE) {
                            monter(as.getNumero());
                        } else {
                            descendre(as.getNumero());
                        }
                        as.setSurLeDepart(false);
                    }
                }
            } else if (as.getStatut() == Ascenseur.ST_EN_ATTENTE) {
                if(!as.getTrajetsEnAttente().isEmpty()){
                    as.setStatut(Ascenseur.ST_INIT_TRAJET);
                    // TODO : Améliorer le choix du trajet prendre le trajet le plus pertinent
                    tr = as.getTrajetsEnAttente().get(0);
                    as.getTrajetsEnAttente().remove(0);
                    as.getTrajetsEnCours().add(tr);
                    as.setVitesse(Ascenseur.VITESSE_1);
                    /* Les informations manquantes seront renseignées lors de la prochaine majPositionAscenseurs
                     * Ici on a juste détecté un nouvel appel */
                } //else if () {
                    // TODO : Gestion du temps d'attente avant repositionnement
                //}
            } else if (as.getStatut() == Ascenseur.ST_INIT_TRAJET) {
                /* A ce niveau l'ascenseur ne doit posséder qu'un appel en cours de trajet */
                prochaineEtage = getProchaineEtageDepart(as.getNumero());
                majMouvement(as.getNumero());
                
                /* Vérifier si l'étage a été atteint */
                if(prochaineEtage.equals(as.getEtageCourant())) {
                    tr = as.getTrajetsEnCours().get(0);
                    if(as.getVitesse() == Ascenseur.VITESSE_1 || as.getVitesse() == Ascenseur.VITESSE_0) {
                        as.setPorteOuverte(true);
                        as.setDelaiAvantProchaineEtage(5);
                        as.setStatut(Ascenseur.ST_EN_COURS_DE_TRAJET);
                        as.setVitesse(Ascenseur.VITESSE_0);
                        as.setDirectionActuel(Ascenseur.IMMOBILE);
                        as.setSurLeDepart(false);
                    } else {
                        /* Arrêt de la simulation, cette erreur ne devrait pas arrivée */
                        as.getTrajetsEnCours().remove(0);
                        throw new VitesseTropEleveException();
                    }
                } else {
                    if(Math.abs(prochaineEtage.getNumero() - as.getEtageCourant().getNumero()) > 2) {
                        accelerer(as.getNumero());
                    } else {
                        ralentir(as.getNumero());
                    }
                    
                    if(as.getDelaiAvantProchaineEtage() == 0) {
                        if(as.getDirectionActuel() == Ascenseur.MONTEE) {
                            monter(as.getNumero());
                        } else {
                            descendre(as.getNumero());
                        }
                        as.setSurLeDepart(false);
                    }
                }
            } else if (as.getStatut() == Ascenseur.ST_ARRIVE) { 
                as.decrementerDelaiAvantProchaineEtage();
                if(as.getDelaiAvantProchaineEtage() == 0) {
                    as.setPorteOuverte(false);
                    as.setStatut(Ascenseur.ST_EN_ATTENTE);
                    as.setVitesse(Ascenseur.VITESSE_0);
                    as.setDirectionActuel(Ascenseur.IMMOBILE);
                    as.setSurLeDepart(false);
                    if(as.getNombrePersonne() != 0) {
                        throw new PersonnePresenteDansAscenseurException("L'ascenseur " + as.getNumero() + " est passé \"en attente\" avec des personnes à l'intérieur !!");
                    }
                }
            }
        }

        System.out.println(ascenseurs.get(1));
    }

    /**
     * Permet de trouver le prochaine etage d'arrivé à atteindre 
     * @param numero
     * @return 
     */
    private Etage getProchaineEtageArrive(Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        Trajet tr = as.getTrajetsEnCours().get(0);
        Etage e = null; // Etage à retourner
        
        if(as.getStatut() == Ascenseur.ST_EN_COURS_DE_TRAJET) {
            if(tr.getSensTrajet() == Trajet.MONTE) {
                e = getEtageArriveMinTrajetsEnCours(numero);
            } else if(tr.getSensTrajet() == Trajet.DESCEND) {
                e = getEtageArriveMaxTrajetsEnCours(numero);
            }
        }
        
        return e;
    }
    
    /**
     * Permet de trouve le prochaine etage de départ à atteindre
     * @param numero
     * @return 
     */
    private Etage getProchaineEtageDepart(Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        Trajet tr = as.getTrajetsEnCours().get(0);
        Etage e = null; // Etage à retourner
        
        if(as.getStatut() == Ascenseur.ST_EN_COURS_DE_TRAJET) {
            /* Recherche de l'étage de départ le plus proche */
            for(Trajet t :as.getTrajetsEnCours()) {
                /* TEST POUR LA MONTE => Il faut trouver l'étage de départ le plus petit */
                if(tr.getSensTrajet() == Trajet.MONTE) {
                    if(as.getEtageCourant().getNumero() <= t.getEtageDepart().getNumero()) {
                        if(e==null) {
                            e = t.getEtageDepart();
                        } else {
                            if(t.getEtageDepart().getNumero() < e.getNumero()) {
                                e = t.getEtageDepart();
                            }
                        }
                    }
                /* TEST POUR LA DESCENTE => Il faut trouver l'étage de départ le plus grand */
                } else {
                    if(as.getEtageCourant().getNumero() >= t.getEtageDepart().getNumero()) {
                        if(e==null) {
                            e = t.getEtageDepart();
                        } else {
                            if(t.getEtageDepart().getNumero() > e.getNumero()) {
                                e = t.getEtageDepart();
                            }
                        }
                    }
                }
            }
        } else if(as.getStatut() == Ascenseur.ST_INIT_TRAJET) {
            /* Il n'y a qu'un trajet en cours  dans ce cas .. */
            e = tr.getEtageDepart();
        } else if(as.getStatut() == Ascenseur.ST_REPOSITIONNEMENT) {
            e = as.getEtageInitial();
        }
        
        return e;
    }
    
    /**
     * Augmente la vitesse de l'ascenseur d'un cran
     * Et réinitialise le compteur delaiAvantProchaineEtage en fonction
     * de la nouvelle vitesse donnée
     * Gère le delaiAvantProchaineEtage
     * @param numero 
     */
    private void accelerer(Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        
        if(as.getDelaiAvantProchaineEtage()==0) {
            if(as.getVitesse() != Ascenseur.VITESSE_3) {
                if(as.getVitesse() == Ascenseur.VITESSE_2) {
                    as.setVitesse(Ascenseur.VITESSE_3);
                } else if(as.getVitesse() == Ascenseur.VITESSE_1) {
                    as.setVitesse(Ascenseur.VITESSE_2);
                } else {
                    as.setVitesse(Ascenseur.VITESSE_1);
                }
            }
        } else {
            as.decrementerDelaiAvantProchaineEtage();
        }
    }
    
    /**
     * Ralentie la vitesse de l'ascenseur d'un cran
     * Et réinitialise le compteur delaiAvantProchaineEtage en fonction de
     * la nouvelle vitesse
     * Gère le delaiAvantProchaineEtage
     * @param numero 
     */
    private void ralentir(Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        
        /* La méthode setVitesse réinitialise le délaiAvantProchaineEtage */
        if(as.getDelaiAvantProchaineEtage()==0) {
            if(as.getVitesse() != Ascenseur.VITESSE_0) {
                if(as.getVitesse() == Ascenseur.VITESSE_3) {
                    as.setVitesse(Ascenseur.VITESSE_2);
                } else if(as.getVitesse() == Ascenseur.VITESSE_2) {
                    as.setVitesse(Ascenseur.VITESSE_1);
                } else {
                    // Pour raffraîchir le delai avant prochaine etage
                    as.setVitesse(Ascenseur.VITESSE_1);
                }
                    /*else {
                    as.setVitesse(Ascenseur.VITESSE_0);
                }*/
            }
        } else {
            as.decrementerDelaiAvantProchaineEtage();
        }
    }
    
    /**
     * Met à jour le mouvement actuel de l'ascenseur en fonction de
     * ses trajets en cours.
     * /!\ Les trajets en cours d'un ascenseur vont tous dans le même sens !
     * @param numero 
     */
    private void majMouvement(Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        Trajet tr = as.getTrajetsEnCours().get(0);
        
        if(as.getStatut() == Ascenseur.ST_INIT_TRAJET) {
            /* dans ce cas l'ascenseur doit rejoindre l'étage de départ 
             * et on oublie pas que ce statut implique qu'il n'y a qu'un seul et unique
             * trajet en cours */
            if(as.getEtageCourant().getNumero() < tr.getEtageDepart().getNumero()) {
                as.setDirectionActuel(Ascenseur.MONTEE);
            } else if(as.getEtageCourant().getNumero() > tr.getEtageDepart().getNumero()) {
                as.setDirectionActuel(Ascenseur.DESCENTE);
            }
        } else if (as.getStatut() == Ascenseur.ST_EN_COURS_DE_TRAJET) {
            /* Dans ce cas l'ascenseur doit rejoindre l'étage d'arrivé/départ suivant  et il suit forcément la
             * direction du trajet */
            if(!as.isPorteOuverte()) {
                if(tr.getSensTrajet() == Trajet.MONTE) {
                    as.setDirectionActuel(Ascenseur.MONTEE);
                } else if(tr.getSensTrajet() == Trajet.DESCEND){
                    as.setDirectionActuel(Ascenseur.DESCENTE);
                }
            } else {
                as.setDirectionActuel(Ascenseur.IMMOBILE);
            }
        } else {
            // TODO : Gérer le repositionnement automatique
            as.setDirectionActuel(Ascenseur.IMMOBILE);
        }
    }
    
    /**
     * Retourne la conso actuel de l'ascenseur
     * @param numero
     * @return 
     */
    private int getConso(Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        
        if(as.getVitesse() == Ascenseur.VITESSE_0) {
            return 0;
        } else if(as.getDirectionActuel() == Ascenseur.MONTEE) {
            return Ascenseur.CONSO_MONTEE;
        } else if(as.getDirectionActuel() == Ascenseur.DESCENTE) {
            return Ascenseur.CONSO_DESCENTE;
        } else {
            return 0;
        }
    }
    
    /**
     * Permet à l'ascenseur de monter d'un étage
     * /!\ Ne gère pas l'accélération des ascenseurs 
     * @param numero 
     */
    private void monter(Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        Etage etageCourant = as.getEtageCourant();
        
        if(etageCourant.getNumero() < etageControleur.getEtageMax()) {
            as.setEtageCourant(etageControleur.getEtageByNumero(etageCourant.getNumero()+1));
            as.incrementerEnergieConsommee(Ascenseur.CONSO_MONTEE);
            
            for(Trajet t:as.getTrajetsEnCours()) {
                t.incrementerEnergieConsommee(Ascenseur.CONSO_MONTEE);
            }
        }
    }
    
    /**
     * Permet à l'ascenseur de descendre d'un étage
     * /!\ Ne gère pas l'accélération des ascenseurs
     * @param numero 
     */
    private void descendre(Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        Etage etageCourant = as.getEtageCourant();
        
        if(etageCourant.getNumero() > etageControleur.getEtageMin()) {
            as.setEtageCourant(etageControleur.getEtageByNumero(etageCourant.getNumero()-1));
            as.incrementerEnergieConsommee(Ascenseur.CONSO_DESCENTE);
            
            for(Trajet t:as.getTrajetsEnCours()) {
                t.incrementerEnergieConsommee(Ascenseur.CONSO_DESCENTE);
            }
        }
    }
    
    public int getEtageCourant(Integer numero) {
        return ascenseurs.get(numero).getEtageCourant().getNumero();
    }
    
    public Set<Integer> getAllNumeroAscenseur() {
        return ascenseurs.keySet();
    }
    
    public int calculerCoutConsoTrajetsEnCours(Integer numero) {
        int cout=0;
        
        Ascenseur ascenseur = ascenseurs.get(numero);
        
        if(ascenseur.getStatut()==Ascenseur.ST_EN_COURS_DE_TRAJET) {
            if(ascenseur.getDirectionActuel() == Ascenseur.MONTEE) {
                cout = Ascenseur.CONSO_MONTEE * Math.abs(ascenseur.getEtageCourant().getNumero() - getEtageArriveMaxTrajetsEnCours(numero).getNumero());
            } else {
                cout = Ascenseur.CONSO_DESCENTE * Math.abs(ascenseur.getEtageCourant().getNumero() - getEtageArriveMinTrajetsEnCours(numero).getNumero());
            }
        }
        
        return cout;
    }
    
    /**
     * Recherche les trajets de l'ascenseur spécifié qui possèdent un étage
     * de départ égale à l'étage spécifié
     * 
     * @param ed Etage de départ recherché
     * @param numero Numéro de l'ascenseur
     * @return 
     */
    private List<Trajet> getTrajetEtageDepartEgale(Etage ed,Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        List<Trajet> trajets = new ArrayList<Trajet>();
        
        for(Trajet tr : as.getTrajetsEnCours()) {
            if(tr.getEtageDepart().equals(ed)) {
                trajets.add(tr);
            }
        }
        
        return trajets;
    }
    
    /**
     * Recherche les trajets de l'ascenseur spécifié qui possèdent un étage
     * d'arrivé égale à l'étage spécifié
     * 
     * @param ed Etage de départ recherché
     * @param numero Numéro de l'ascenseur
     * @return 
     */
    private List<Trajet> getTrajetEtageArriveEgale(Etage ea,Integer numero) {
        Ascenseur as = ascenseurs.get(numero);
        List<Trajet> trajets = new ArrayList<Trajet>();
        
        for(Trajet tr : as.getTrajetsEnCours()) {
            if(tr.getEtageArrive().equals(ea)) {
                trajets.add(tr);
            }
        }
        
        return trajets;
    }
    
    public Etage getEtageArriveTrajetsEnCours(Integer numero) {
        Ascenseur ascenseur = ascenseurs.get(numero);
        
        if(ascenseur.getDirectionActuel() == Ascenseur.MONTEE) {
            return getEtageArriveMaxTrajetsEnCours(numero);
        } else {
            return getEtageArriveMinTrajetsEnCours(numero);
        }
    }
    
    private Etage getEtageDepartMaxTrajetsEnCours(Integer numero) {
        Ascenseur ascenseur = ascenseurs.get(numero);
        Trajet tr = Collections.max(ascenseur.getTrajetsEnCours(),new Comparator<Trajet>() {
            @Override
            public int compare(Trajet t, Trajet t1) {
                return t.getEtageDepart().compareTo(t1.getEtageDepart());
            }
        });
        
        return tr.getEtageDepart();
    }
    
    private Etage getEtageDepartMinTrajetsEnCours(Integer numero) {
        Ascenseur ascenseur = ascenseurs.get(numero);
        
        Trajet tr = Collections.min(ascenseur.getTrajetsEnCours(),new Comparator<Trajet>() {
            @Override
            public int compare(Trajet t, Trajet t1) {
                return t.getEtageDepart().compareTo(t1.getEtageDepart());
            }
        });
        
        return tr.getEtageDepart();
    }
    
    private Etage getEtageArriveMaxTrajetsEnCours(Integer numero) {
        Ascenseur ascenseur = ascenseurs.get(numero);
        
        Trajet tr = Collections.max(ascenseur.getTrajetsEnCours(),new Comparator<Trajet>() {
            @Override
            public int compare(Trajet t, Trajet t1) {
                return t.getEtageArrive().compareTo(t1.getEtageArrive());
            }
        });
        
        return tr.getEtageArrive();
    }
    
    private Etage getEtageArriveMinTrajetsEnCours(Integer numero) {
        Ascenseur ascenseur = ascenseurs.get(numero);
                
        Trajet tr = Collections.min(ascenseur.getTrajetsEnCours(),new Comparator<Trajet>() {
            @Override
            public int compare(Trajet t, Trajet t1) {
                return t.getEtageArrive().compareTo(t1.getEtageArrive());
            }
        });
        
        return tr.getEtageArrive();
    }
    
    public void ajouterObserveur(Observeur o) {
        for(Ascenseur as: ascenseurs.values()) {
            as.ajouterObserveur(o);
        }
    }
    
    public Ascenseur getAscenseur(Integer numero) {
        return ascenseurs.get(numero);
    }
}
